/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* SCRMGR front end to join(1) */

#include <stdio.h>
#include <getopt.h>
#include <setjmp.h>
#include <sys/types.h>		/* for stat */
#include <sys/stat.h>		/* for stat */
#include "smlib.h"
#include "smio.h"
#include "smjoin.def"

CHAR *smjoin_rcsid(void) {
    return("$Id: smjoin.c,v 1.6 2008/02/19 13:15:50 bhepple Exp $");
}
static struct {
    CHAR	*rev_string;
    CHAR 	last_filename1[MAXPATHLEN + 1];
    CHAR 	last_filename2[MAXPATHLEN + 1];
    SM_SCREEN	screen1;
    SM_SCREEN	screen2;
    SM_FIELD	output_field_fld;
    SM_FIELD	join_field_fld;
    SM_FIELD	file1_fld;
    SM_FIELD	file2_fld;
} global;

SM_FILE	screenset;

jmp_buf		NoMoreHeap;

static CHAR *usage_msg[] = {
"Provide an easy to use front end to join(1).",
"",
"screenfile   : a SCRMGR screen definition file.",
"screenname   : a SCRMGR name in screenfile to use (default = first).",
NULL
};

#if !READ_DAT_AT_RUNTIME
static CHAR *smjoin_dat[] = {
#include "smjoin.dat"
NULL,
};
#endif

static void usage()
{
    CHAR **c;
    
    fprintf(stderr, "%s of %s.\n", global.rev_string, global.pname);
    fprintf(stderr, 
"Usage: %s [screenfile1[:screenname1[:screenname2]] [screenfile2[:screenname2]]\n", global.pname);
    
    c = usage_msg;
    while (*c) {
        fputs(*c, stderr);
        fputc('\n', stderr);
        c++;
    }
    exit(2);
}

void sm_error_exit ARG1(CHAR *, msg)
{
    sm_term();
    puts(msg);
    exit(1);
}

void re_fail ARG2(char *, msg, char, op)
{
    sm_message(msg);
}

static void update_n_paint ARG3(int, window, SM_FIELD, field, CHAR *, value)
{
    sm_fld_set(field, value);
    sm_paint_fld(window, field->c_value, field, 1, 0);
}

static COMMAND field_name_trigger ARG3(SM_FIELD, 	field, 
				       COMMAND, 	command, 
				       int, 		window)
{
    int		field_num;
    char	buf[40];

    if (*sm_fld_enq(field)) {
	if ((global.screen1 && 
	     (sm_fld_open(global.screen1, sm_fld_enq(field)) == NULL)) ||
	    (global.screen2 && 
	     (sm_fld_open(global.screen2, sm_fld_enq(field)) == NULL)))
	    sm_message("No such field");
	    if ((command != SM_DELETE_DATA_C) &&
		(command != SM_NO_SAVE_EXIT_C))
		return(0); /* suppress the move off this field */
	    else
		return(command);
	
	if (sm_calc_join_params(global.screen1, 
				sm_fld_enq(field), 
				&field_num) == 0) {
	    sprintf(buf, "%d", field_num);
	    update_n_paint(window, sm_fld_open(join_scr, "field_num1"), buf);
	    /* reset the cursor in the right place etc */
	    sm_paint_fld(window, field->c_value, field, 1, 0);
	}
	if (sm_calc_join_params(global.screen2, 
				sm_fld_enq(field), 
				&field_num) == 0) {
	    sprintf(buf, "%d", field_num);
	    update_n_paint(window, sm_fld_open(join_scr, "field_num2"), buf);
	    /* reset the cursor in the right place etc */
	    sm_paint_fld(window, field->c_value, field, 1, 0);
	}
    }
    return(command);
}

/* Exported to the SCRMGR package */
f_ptr sm_get_field_trigger ARG1(SM_FIELD, field)
{
    if (strcmp(field->trigger_fn, "field_name_trigger") == 0)
	return(field_name_trigger);
    return(NULL);
}

static void add_output_popups ARG2(SM_SCREEN, screen, SM_FIELD, file_fld)
{
    CHAR 	buf[100];
    
    if (screen) {
	if (screen->data_field) {
	    CHAR **p;
	    
	    for (p = (CHAR **) firstArrayItemp(screen->data_field); 
		 p && *p; 
		 p = (CHAR **) nextArrayItemp(screen->data_field, p)) {
		strcpy(buf, sm_fld_enq(file_fld));
		strcat(buf, ":");
		strcat(buf, *p);
		sm_add_field_popup_item(global.output_field_fld, buf);
	    }
	} else {
	    SM_FIELD	*fieldp;
	    
	    for (fieldp = sm_first_fld(screen);
		 fieldp && *fieldp;
		 fieldp = sm_next_fld(fieldp)) {
		strcpy(buf, sm_fld_enq(file_fld));
		strcat(buf, ":");
		strcat(buf, (*fieldp)->field_name);
		sm_add_field_popup_item(global.output_field_fld, buf);
	    }
	}
    }
}

/* Note: this writes to its input - filename */
static SM_SCREEN load_dat_file ARG4(SM_FILE, 	screenset, 
				    CHAR *, 	filename,
				    SM_SCREEN,	other_screen,
				    SM_FIELD,	file_fld)
{
    CHAR	*screenname;
    SM_SCREEN	screen = NULL, *screenp;
    CHAR	*buf = alloca(SM_MAXREC);
    SM_FIELD	*fieldp;
    
    if (filename && *filename) {
	
	smdb_delete_data(join_scr);
	sm_fld_init(file_fld);
	
	screenname = strchr(filename, ':');
	if (screenname)
	    *screenname++ = 0;
	
	if (sm_fopen(screenset, filename) == SM_ERROR)
	    sm_error("Error reading screen file");
	else {
	    sm_fld_set(global.output_field_fld, "");
	    
	    if (screenset->separator == '~')
		sm_scrmgr_screens()->separator = '<';
	    
	    if (screenname)
		screen = sm_scr_open(screenset, screenname);
	    else {
		screenp = sm_first_scr(screenset);
		if (screenp && *screenp) {
		    screen = *screenp;
		    if (screen == other_screen) {
			screenp = sm_next_scr(screenp);
			screen = NULL;
			if (screenp && *screenp)
			    screen = *screenp;
		    }
		}
	    }
	    if (screen) {
		sm_fld_set(file_fld, screen->filename);
		sm_fld_set(sm_fld_open(join_scr, "separator"),
			   &screenset->separator);
	    }	    
	}
	
	/* Store first record */
	sm_screen_to_data(buf, join_scr);
	smdb_write(join_scr, buf);
	smdb_seek(join_scr, (INDEX) 0);
	
	/* Rebuild output_field popups */
	sm_delete_field_popups(global.output_field_fld);
	if (screen)
	    add_output_popups(screen, global.file1_fld);
	if (other_screen)
	    add_output_popups(other_screen, global.file2_fld);
	
	/* Rebuild join_field popups */
	sm_delete_field_popups(global.join_field_fld);
	if (screen)
	    for (fieldp = sm_first_fld(screen);
		 fieldp && *fieldp;
		 fieldp = sm_next_fld(fieldp))
		/* Only put field name in if it's in both or there's only 
		   one screen */
		if (!other_screen ||
		    sm_fld_open(other_screen, (*fieldp)->field_name))
		    sm_add_field_popup_item(global.join_field_fld, 
					    (*fieldp)->field_name);
	if (other_screen)
	    for (fieldp = sm_first_fld(other_screen);
		 fieldp && *fieldp;
		 fieldp = sm_next_fld(fieldp))
		/* Only put field name in if it's in both or there's only 
		   one screen */
		if (!screen || 
		    sm_fld_open(screen, (*fieldp)->field_name))
		    sm_add_field_popup_item(global.join_field_fld, 
					    (*fieldp)->field_name);
	
	/* See if there is a common field */
	if (screen && other_screen)
	    for (fieldp = sm_first_fld(screen);
		 fieldp && *fieldp;
		 fieldp = sm_next_fld(fieldp))
		if (sm_fld_open(other_screen, (*fieldp)->field_name)) {
		    sm_fld_set(global.join_field_fld, (*fieldp)->field_name);
		    break;
		}
	
	if (screen && other_screen) {
	    sm_fld_prot(sm_fld_open(join_scr, "field_num1"), 1);
	    sm_fld_prot(sm_fld_open(join_scr, "field_num2"), 1);
	    sm_fld_prot(global.join_field_fld, 0);
	} else {
	    sm_fld_prot(sm_fld_open(join_scr, "field_num1"), 0);
	    sm_fld_prot(sm_fld_open(join_scr, "field_num2"), 0);
	    sm_fld_prot(global.join_field_fld, 1);
	    smdb_read_file(NULL, join_scr, screenset->untabify);
	    screen = NULL;
	}
    }
    return(screen);
}

struct load_file_s {
    f_ptr	destructor;
    int		fileno;
};

static int load_file_destructor ARG1(void *, object)
{
    free(object);
    return(0);
}

CALLBACK(load_file_done)
{
    struct load_file_s	*load_file = (struct load_file_s *) callback_data;
    if (command == SM_YES_C) {
	smdb_delete_data(join_scr);
	sm_message("Loading ...");
	if (load_file->fileno == 1) {
	    _sm_clear_out(SM_USER_SCREENS);
	    strcpy(global.last_filename1, sm_get_prompt_value());
	    global.screen1 = load_dat_file(sm_user_screens(),
					   global.last_filename1,
					   global.screen2,
					   global.file1_fld);
	} else {
	    _sm_clear_out(SM_EXTRA_SCREENS);
	    strcpy(global.last_filename2, sm_get_prompt_value());
	    global.screen2 = load_dat_file(sm_extra_screens(),
					   global.last_filename2,
					   global.screen1,
					   global.file2_fld);
	}
	sm_message("");
    }
    return(command);
}

int lambda ARG2(int, command, SM_SCREEN, screen)
{
    switch (command) {
    default:
	break;
    }
    return(command);
}

static int build_join_line ARG2(CHAR *, buf, SM_SCREEN, screen)
{
    CHAR	*record, c;
    int		field_num, char_start, char_end, output_all_fields;
    struct stat	statbuf;

    strcpy(buf, "join ");

    record = smdb_seek(screen, (INDEX) 0);
    if (!record || !*record)
	return(1);
    sm_data_to_screen(screen, record, 0);

    c = *sm_fld_get(screen, "separator");
    buf = strchr(buf, 0);
    if (c > ' ') { /* Don't put in a -t option for ' ' */
	if (c == '\\')
	    sprintf(buf, "-t'\\\\' ", c);
	else if (c == 't')
	    sprintf(buf, "-t'\t' ");
	else
	    sprintf(buf, "-t'%c' ", c);
    } else if (c == ' ') {
	sm_error(
"WARNING: 'join' does not use separator ' ' like sort, awk, etc.!");
    } else {
	sm_message("Bad separator character");
	return(1);
    }

    output_all_fields = (*sm_fld_get(screen, "output_all") == 'Y');
    while (record) {
	record = smdb_get_current(screen);
	sm_data_to_screen(screen, record, 0);
	field_num = atoi(sm_fld_get(screen, "field_num"));
	char_start = atoi(sm_fld_get(screen, "char_start"));
	char_end = atoi(sm_fld_get(screen, "char_end"));
	if (field_num >= 0) {
	    if (char_start > 0)
		sprintf(buf, "+%d.%d ", 
			field_num, char_start);
	    else
		sprintf(buf, "+%d ", field_num);
	    buf = strchr(buf, 0);
	    if (char_end > 0)
		sprintf(buf, "-%d.%d", field_num, char_end);
	    else
		sprintf(buf, "-%d", field_num + 1);
	    if      (*sm_fld_get(screen, "ignore_case") == 'Y')
		strcat(buf, "f");
	    if      (*sm_fld_get(screen, "month_order") == 'Y')
		strcat(buf, "M");
	    else if (*sm_fld_get(screen, "numeric")     == 'Y')
		strcat(buf, "n");
	    if      (*sm_fld_get(screen, "reverse")     == 'Y')
		strcat(buf, "r");
	    strcat(buf, " ");
	    buf = strchr(buf, 0);
	}
	record = smdb_read_next(screen);
    }
    record = smdb_seek(join_scr, (INDEX) 0);
    sm_data_to_screen(screen, record, 0);

    if (*sm_fld_get(screen, "file1") == 0) {
	sm_message("You must fill in the input filename!");
	return(1);
    }
    if (stat(sm_fld_get(screen, "file1"), &statbuf)) {
	sm_message("File1: No such file!");
	return(1);
    }
    strcat(buf, sm_fld_get(screen, "file1"));
    strcat(buf, " ");

    if (*sm_fld_get(screen, "file2") == 0) {
	sm_message("You must fill in the input filename!");
	return(1);
    }
    if (stat(sm_fld_get(screen, "file2"), &statbuf)) {
	sm_message("File2: No such file!");
	return(1);
    }
    strcat(buf, sm_fld_get(screen, "file2"));
    strcat(buf, " ");

    buf = strchr(buf, 0);
    return(0);
}

static void join_file ARG1(SM_SCREEN, screen)
{
    CHAR	*command = alloca(SM_MAXREC);
    CHAR	*buf = command, *output;
    CHAR	tmp[100];
    int		is_pipe = 0;

    if (*(output = sm_fld_get(screen, "output_file")) != NULL) {
	while (*output && isspace(*output)) 
	    output++;
	if (*output == '|') {
	    is_pipe = 1;
	    output++;
	}
    } else
	output = sm_fld_get(screen, "input_file");
    
    if (is_pipe)
	strcpy(buf, "(");
    else
	strcpy(buf, "(if ");

    buf = strchr(buf, 0);
    if (build_join_line(buf, screen))
	return;

    buf = strchr(buf, 0);
    if (is_pipe)
	sprintf(buf, "| %s)", output);
    else {
	sm_mktemp(tmp, "TXXXXXX");
	sprintf(buf, ">%s; then cp %s %s; fi)", tmp, tmp, output);
    }

    if (smio_system(command, 1) == 0)
	sm_message("Files are joined");
    unlink(tmp);
}

static int save_command ARG2(CHAR *, filename, SM_SCREEN, screen)
{
    CHAR	*buf = alloca(SM_MAXREC);
    FILE	*file;
    int		retval = 0;

    if (backup_file(filename) < 0) {
	sprintf(buf, 
		"Can't make a backup file for \"%s\"", 
		filename);
	sm_message(buf);
    } 
    
    if ((file = fopen(filename, "w")) == NULL) {
	sm_file_error("Can't write to file: ", filename, errno);
	if (file)
	    fclose(file);
	retval = 1;
    } else {
	build_join_line(buf, screen);
	fputs(buf, file);
	fputc('\n', file);
	fclose(file);
	sprintf(buf, "Command line saved in \"%s\".", filename);
	sm_message(buf);
    }
    return(retval);
}

static void display_command ARG1(SM_SCREEN, screen)
{
    CHAR	*buf = alloca(SM_MAXREC);

    build_join_line(buf, screen);
    sm_message(buf);
}

static void view_output ARG1(SM_SCREEN, screen)
{
    CHAR	*buf = alloca(SM_MAXREC), *p;
    
    p = getenv("PAGER");
    if (p && *p)
	strcpy(buf, p);
    else
	strcpy(buf, "more");
    strcat(buf, " ");
    if (*sm_fld_get(screen, "output_file"))
	strcat(buf, sm_fld_get(screen, "output_file"));
    else if (*sm_fld_get(screen, "input_file"))
	strcat(buf, sm_fld_get(screen, "input_file"));
    else {
	sm_message("Nothing to look at yet!");
	return;
    }
    if (strchr(buf, '|'))
	sm_message("Can't look at a pipeline!");
    else
	smio_system(buf, 0);
}

CALLBACK(do_save_command)
{
    FILE	*file;
    CHAR	*filename;

    /* Always entered after sm_prompt */
    if (command == SM_YES_C) {
	filename = sm_get_prompt_value();

	if ((file = fopen(filename, "r")) != NULL) {
	    CHAR	*buf = alloca(100);

	    fclose(file);
	    sprintf(buf, "\"%s\" already exists. Overwrite it?", filename);
	    if (sm_confirm(buf) != SM_YES_C)
		return(SM_QUIT_C);
	}
	save_command(filename, join_scr);
	command = SM_POPCALLBACK;
    }

    return(command);
}

void setup_save_command()
{
    CHAR	pwd[MAXPATHLEN + 1];

    GETCWD(pwd, MAXPATHLEN);
    sm_add_callback(do_save_command, NULL);
    sm_setup_prompt("Enter file for the command line (current directory is:)", 
		    pwd, 
		    NULL,
		    NULL);
}

CALLBACK(smjoin_main_loop)
{
    switch(command) {
    case LOAD_FILE_1:
    case LOAD_FILE_2:
	{
	    CHAR	pwd[MAXPATHLEN + 1];
	    CHAR	msg[100];
	    struct load_file_s *load_file;

	    load_file = (struct load_file_s *) xmalloc((INDEX) sizeof(struct load_file_s));
	    load_file->destructor = load_file_destructor;
	    load_file->fileno = (command == LOAD_FILE_1)? 1: 2;
	    sm_add_callback(load_file_done, load_file);
	    GETCWD(pwd, MAXPATHLEN);
	    sprintf(msg, 
		    "Enter screen file for file %d (current directory is:)", 
		    fileno);
	    sm_setup_prompt(msg, 
			    pwd,
			    global.last_filename1,
			    NULL);
	    return(0);
	}	
	break;
	
    case JOIN_FILES:
	join_file(join_scr);
	break;

    case DISPLAY_COMMAND:
	display_command(join_scr);
	break;

    case VIEW_OUTPUT:
	view_output(join_scr);
	break;

    case SAVE_COMMAND:
	setup_save_command();
	return(0);

    case SM_CLEAR_FILE_C:
	if (!smdb_file_changed(join_scr) || 
	    (sm_confirm("Clear join keys?") == SM_YES_C)) {
	    smdb_delete_data(join_scr);
	    global.last_filename1[0] = 0;
	    sm_message("File cleared");
	}
	break;
	
    case SM_EXIT_C:
    case SM_NO_SAVE_EXIT_C:
	return(command);
	break;

    default: /* Probably a return from one of the sub routines */
	break;
    }

    sm_setup_edit(SM_BASE_WINDOW, 
		  join_scr, 
		  SM_EXIT_C, 
		  lambda, 
		  join_skey,
		  NULL);
    return(0);
}

int main ARG2(int, argc, CHAR **, argv)
{
    int			c;

    /* Temporary back door */
    if (setjmp(NoMoreHeap)) {
	alloca(0);
	sm_error_exit(sm_mem_error);
    }

    global.rev_string = "$Revision: 1.6 $";
    global.last_filename1[0] = 0;
    global.last_filename2[0] = 0;
    global.screen1 = NULL;
    global.screen2 = NULL;

    sm_pre_init(argc, argv);

    /* Get rid of X options and enter X if appropriate */
    sm_init(1, argc, argv);

    while ((c = getopt(argc, argv, "?")) != EOF)
        switch (c) {
        default:  
	    /* Unfortunately, if we've entered X in sm_init, then any error 
	       messages disappear */
	    usage(); 
	    break;
        }
    
    if ((optind < argc) && (strlen(argv[optind]) != 0))
	/* Use first argument as join file 1 */
	strcpy(global.last_filename1, argv[optind++]);

    if ((optind < argc) && (strlen(argv[optind]) != 0))
	/* Use second argument as join file 2 */
	strcpy(global.last_filename2, argv[optind++]);

#ifdef SM_DEBUG
    smio_flushinp();
#endif

    screenset = sm_scrmgr_screens();

#if READ_DAT_AT_RUNTIME
    {
	CHAR	buf[MAXPATHLEN + 1];

	buf[0] = 0;
#if NEEDS_PATH_FOR_PROGS
	strcpy(buf, sm_program_directory);
#endif
	strcat(buf, "smjoin.dat");
	if (sm_fopen(screenset, buf) == SM_ERROR)
	    sm_error_exit("Error reading smjoin.dat!");
    }
#else
    if (sm_dbopen(screenset, smjoin_dat) == SM_ERROR)
	sm_error_exit("Error reading internal data block!");
#endif

#include "smjoin.opn"

    if ((global.join_field_fld = sm_fld_open(join_scr, "join_field")) == NULL)
        sm_error_exit("Can't open field join_field");
    if ((global.output_field_fld = sm_fld_open(join_scr, "output_field")) == NULL)
        sm_error_exit("Can't open field output_field");
    if ((global.file1_fld = sm_fld_open(join_scr, "file1")) == NULL)
        sm_error_exit("Can't open field file1");
    if ((global.file2_fld = sm_fld_open(join_scr, "file2")) == NULL)
        sm_error_exit("Can't open field file2");
	
    /* Back door for when we run out of heap - horrible structure, but
       what can one do ?*/

    if (setjmp(NoMoreHeap)) {
	alloca(0);

	/* Prevent infinite loop */
	if (setjmp(NoMoreHeap)) {
	    alloca(0);
	    sm_error_exit(sm_mem_error);
	}

	sm_message(sm_mem_error);

	if (setjmp(NoMoreHeap)) {
	    /* Oh boy - really serious! */
	    alloca(0);
	    sm_error_exit("Ran out of heap - file(s) were not saved! Sorry!");
	}

	alloca(0);

	if (setjmp(NoMoreHeap) == 0)
	    sm_term();
	exit(1);
    }

    global.screen1 = load_dat_file(sm_user_screens(),  
				   global.last_filename1,
				   global.screen2,
				   global.file1_fld);

    global.screen2 = load_dat_file(sm_extra_screens(), 
				   global.last_filename2,
				   global.screen1,
				   global.file2_fld);

    /*************/
    /* Main loop */
    /*************/

    sm_message(global.rev_string);

    sm_add_callback(smjoin_main_loop, NULL);	
    sm_setup_edit(SM_BASE_WINDOW, 
		  join_scr, 
		  SM_EXIT_C, 
		  lambda, 
		  join_skey,
		  NULL);
    sm_main_loop();
    sm_term();
    exit(0);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
